#pragma once

#include    <windows.h>
#include    <wslapi.h>
#include    <string>
#include    <cstdio>

/**
 * WSL APIs
 */
class Api {
    typedef BOOL    (STDAPICALLTYPE * API_IS_REGISTERED)(PCWSTR);
    typedef HRESULT (STDAPICALLTYPE * API_REGISTER)(PCWSTR, PCWSTR);
    typedef HRESULT (STDAPICALLTYPE * API_UNREGISTER)(PCWSTR);
    typedef HRESULT (STDAPICALLTYPE * API_CONFIGURE)(PCWSTR, ULONG, WSL_DISTRIBUTION_FLAGS);
    typedef HRESULT (STDAPICALLTYPE * API_LAUNCH_INTERACTIVE)(PCWSTR, PCWSTR, BOOL, DWORD *);
    typedef HRESULT (STDAPICALLTYPE * API_LAUNCH)(PCWSTR, PCWSTR, BOOL, HANDLE, HANDLE, HANDLE, HANDLE *);

public:
    Api(const std::string & distro, const std::string & distro_repo);
    ~Api();

    /**
     * Check environment for install Linux subsystem on Windows
     */
    inline BOOL CheckEnvironment() const {
        return _dll != nullptr &&
            _is_registered != nullptr &&
            _register != nullptr &&
            _unregister != nullptr &&
            _config != nullptr &&
            _launch_interactive != nullptr &&
            _launch != nullptr;
    }

    /**
     * Has this distribution been registered?
     */
    inline BOOL IsRegistered() const {
        return _is_registered(_distro.c_str());
    }

    /**
     * Configure this distribution
     * 
     * \param uid Default user id
     * \param flags Flags for this distribution \see WSL_DISTRIBUTION_FLAGS
     */
    inline BOOL Configure(ULONG uid, WSL_DISTRIBUTION_FLAGS flags) const {
        HRESULT hr = _config(_distro.c_str(), uid, flags);
        if (FAILED(hr)) {
            wprintf(L"Configure distribution[%ls] failed : %0x!\n", _distro.c_str(), hr);
            return FALSE;
        }
        return TRUE;
    }

    /**
     * Run command interactively
     * 
     * \param cmd Bash command
     * \param use_cur_workdir Using current working directory or user's home dir(~)
     * \param exit_code Command exit code.
     */
    inline BOOL LaunchInteractive(PCWSTR cmd, BOOL use_cur_workdir, DWORD * exit_code) const {
        HRESULT hr = _launch_interactive(_distro.c_str(), cmd, use_cur_workdir, exit_code);
        if (FAILED(hr)) {
            wprintf(L"Running command [%ls] on distribution[%ls] failed : %0x!\n", cmd, _distro.c_str(), hr);
            return FALSE;
        }
        return TRUE;
    }

    /**
     * Running command
     * 
     * \param cmd Bash command
     * \param use_cur_workdir Using current working directory or user's home dir(~)
     * \param in Standard input redirect
     * \param out Standard output redirect
     * \param err Standard error redirect
     * \param process Running process
     */
    inline BOOL Launch(PCWSTR cmd, BOOL use_cur_workdir, HANDLE in, HANDLE out, HANDLE err, HANDLE * process) const {
        HRESULT hr = _launch(_distro.c_str(), cmd, use_cur_workdir, in, out, err, process);
        if (FAILED(hr)) {
            wprintf(L"Running command [%ls] on distribution[%ls] failed : %0x!\n", cmd, _distro.c_str(), hr);
            return FALSE;
        }
        return TRUE;
    }

    /**
     * Set default login user
     * 
     * \param name User's name
     */
    inline VOID SetDefaultUser(const std::wstring & name) const {
        ULONG uid = GetUID(name);
        if (uid == (ULONG)-1) return;
        (VOID)Configure(uid, WSL_DISTRIBUTION_FLAGS_DEFAULT);
    }

    /**
     * Get user's id in Linux system by name
     * 
     * \param name User's name
     * \return Linux user id
     */
    ULONG GetUID(const std::wstring & name) const;

    /**
     * Start to install current distro for network
     */
    BOOL Install() const;

    /**
     * Create new user after install
     */
    VOID CreateUser() const;

    /**
     * Uninstall this distro
     */
    VOID Uninstall() const;

private:
    std::wstring _distro;
    std::string _repo;
    HMODULE _dll;

    API_IS_REGISTERED _is_registered;
    API_REGISTER _register;
    API_UNREGISTER _unregister;
    API_CONFIGURE _config;
    API_LAUNCH_INTERACTIVE _launch_interactive;
    API_LAUNCH _launch;
};